package none.rg.gantt.xmlio;


import none.rg.gantt.model.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.util.*;
import java.io.*;


/**
 * Singleton class for saving the chart into XML file.
 * @author Rodion Gorkovenko
 */
public class ChartSaver {
    
    
    private static ChartSaver instance = new ChartSaver();
    
    private DocumentBuilder documentBuilder;
    
    
    private ChartSaver() {
        try {
            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new Error("Error while initializing XML writer: " + e);
        } // catch
    } // ChartSaver
    
    
    /**
     * Writes Chart to XML file specifying starting times of tasks, critical path etc.
     * @param chart The chart to be written.
     * @param fileName The name of the file to write.
     * @throws IOException in case of IO errors.
     */
    public void writeToFile(Chart chart, String fileName) throws IOException {
        try (OutputStream out = new FileOutputStream(fileName)) {
            out.write(serialize(chart).getBytes());
        } finally {
        } // finally
    } // writeToFile
    
    
    /** Returns XML representation of the chart (with times, critical path etc.) */
    public synchronized String serialize(Chart chart) {
        Transformer tr;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        
        try {
            tr = TransformerFactory.newInstance().newTransformer();
            tr.setOutputProperty(OutputKeys.INDENT, "yes");
            tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            tr.transform(new DOMSource(makeDom(chart)),
                    new StreamResult(out));
        } catch (TransformerException e) {
            throw new Error("Exception while serializing to XML: " + e);
        } // catch
        
        return out.toString();
    } // serialize
    
    
    private Document makeDom(Chart chart) {
        Document xml;
        Element chartElem;
        Map<Integer, Task> tasks = chart.getTaskMap();
        
        xml = documentBuilder.newDocument();
        
        chartElem = xml.createElement(Names.TAG_CHART);
        if (chart.isCalculable()) {
            chartElem.setAttribute(Names.ATTR_CHART_FINISH,
                    Integer.toString(chart.getFinishTime()));
        } else {
            chartElem.setAttribute(Names.ATTR_CHART_IMPOSSIBLE,
                    Boolean.TRUE.toString());
        } // else
        xml.appendChild(chartElem);
        
        for (Task task : tasks.values()) {
            Milestone milestone = null;
            Group group = null;
            Element taskElem = xml.createElement(Names.TAG_TASK);
            taskElem.setAttribute(Names.ATTR_TASK_TITLE, task.getTitle());
            
            if (!task.getSpecialType().equals(Task.SPECIAL_TYPE)) {
                if (task.getSpecialType().equals(Group.SPECIAL_TYPE)) {
                    group = (Group) task;
                } // if
                if (task.getSpecialType().equals(Milestone.SPECIAL_TYPE)) {
                    milestone = (Milestone) task;
                } // if
                taskElem.setAttribute(Names.ATTR_TASK_SPECIAL, task.getSpecialType());
            } // if
            
            if (group == null && milestone == null) {
                taskElem.setAttribute(Names.ATTR_TASK_DUR,
                    Integer.toString(task.getDuration()));
            } // if
            taskElem.setAttribute(Names.ATTR_TASK_START,
                    Integer.toString(task.getStart()));
            if (task.getStart() + task.getDuration() != task.getFinish() && group != null) {
                taskElem.setAttribute(Names.ATTR_TASK_FINISH,
                        Integer.toString(task.getFinish()));
            } // if
            if (!task.isFixedDuration() && group == null) {
                taskElem.setAttribute(Names.ATTR_TASK_RUBBER, Boolean.TRUE.toString());
            } // if
            if (!task.getCritical().equals(Task.Critical.NONE)) {
                taskElem.setAttribute(Names.ATTR_TASK_CRITICAL,
                        Names.criticalFlags.get(task.getCritical()));
            } // if
            
            for (Link.Id linkId : task.getPredIds()) {
                Link link = task.getPred(linkId);
                Element depElem;
                
                if (link.getPred().isOfSpecialType(Group.SPECIAL_TYPE)
                        && link.getType().equals(Link.Type.START_START)
                        && ((Group) link.getPred()).haveChild(task)) {
                    continue;
                } // if
                
                if (group != null
                        && link.getType().equals(Link.Type.FINISH_FINISH)
                        && group.haveChild(link.getPred())) {
                    continue;
                } // if
                
                depElem = xml.createElement(Names.TAG_DEP);
                depElem.setAttribute(Names.ATTR_DEP_PRED, link.getPred().getTitle());
                if (!link.getType().equals(Link.Type.FINISH_START)) {
                    depElem.setAttribute(Names.ATTR_DEP_TYPE,
                            Names.linkTypesStr.get(link.getType()));
                } // if
                taskElem.appendChild(depElem);
            } // for
            
            if (group != null) {
                for (Task child : group.viewChildren()) {
                    Element childElem = xml.createElement(Names.TAG_CHILD);
                    childElem.setAttribute(Names.ATTR_TASK_TITLE, child.getTitle());
                    taskElem.appendChild(childElem);
                } // for
            } // if
            
            chartElem.appendChild(taskElem);
        } // for
        
        return xml;
    } // makeDom
    
    
    /** Returns instance of the class */
    public static ChartSaver getInstance() { return instance; }
    
    
} // class ChartSaver
